package com.szcinda.service.dto.carrier;

import com.szcinda.repository.*;
import com.szcinda.service.PageResult;
import com.szcinda.service.SnowFlakeFactory;
import com.szcinda.service.dto.client.ClientVehiclePriceDto;
import com.szcinda.service.dto.line.VehicleFeeQueryDto;
import com.szcinda.service.dto.linePrice.LinePriceService;
import com.szcinda.service.dto.log.LogCreateDto;
import com.szcinda.service.dto.log.LogService;
import com.szcinda.service.dto.organization.OrganizationService;
import com.szcinda.service.dto.role.RoleRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.Predicate;
import java.time.Instant;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class CarrierServiceImpl implements CarrierService {

    private final CarrierRepository carrierRepository;
    private final SnowFlakeFactory snowFlakeFactory;
    private final ContractRepository contractRepository;
    private final RateRepository rateRepository;
    private final ClientVehiclePriceRepository clientVehiclePriceRepository;
    private final VehicleRepository vehicleRepository;
    private final OrganizationService organizationService;
    private final LogService logService;
    private final RoleRepository roleRepository;
    private final LinePriceService linePriceService;
    private final UserRepository userRepository;
    private final BaoGuanFeeRepository baoGuanFeeRepository;

    public CarrierServiceImpl(CarrierRepository carrierRepository, ContractRepository contractRepository, RateRepository rateRepository, ClientVehiclePriceRepository clientVehiclePriceRepository, VehicleRepository vehicleRepository, OrganizationService organizationService, LogService logService, RoleRepository roleRepository, LinePriceService linePriceService, UserRepository userRepository, BaoGuanFeeRepository baoGuanFeeRepository) {
        this.carrierRepository = carrierRepository;
        this.contractRepository = contractRepository;
        this.rateRepository = rateRepository;
        this.clientVehiclePriceRepository = clientVehiclePriceRepository;
        this.vehicleRepository = vehicleRepository;
        this.organizationService = organizationService;
        this.logService = logService;
        this.roleRepository = roleRepository;
        this.linePriceService = linePriceService;
        this.userRepository = userRepository;
        this.baoGuanFeeRepository = baoGuanFeeRepository;
        this.snowFlakeFactory = SnowFlakeFactory.getInstance();
    }

    @Override
    public void create(CarrierCreateDto carrierCreateDto) {
        Assert.hasText(carrierCreateDto.getCode(), "供应商编号不能为空");
        Assert.hasText(carrierCreateDto.getName(), "供应商简称不能为空");
        Assert.hasText(carrierCreateDto.getFullName(), "供应商全称不能为空");
        Assert.hasText(carrierCreateDto.getOrganizationId(), "组织机构ID不能为空");
        List<Carrier> carriers = carrierRepository.findByOrganizationId(carrierCreateDto.getOrganizationId());
        Assert.isTrue(carriers.stream().noneMatch(carrier -> carrier.getName().equals(carrierCreateDto.getName())), String.format("当前机构经存在相同供应商简称【%s】，请更改再保存", carrierCreateDto.getName()));
        Carrier carrier = new Carrier();
        BeanUtils.copyProperties(carrierCreateDto, carrier);
        carrier.setId(snowFlakeFactory.nextId("CA"));
        carrier.setCode(carrier.getCode().toUpperCase());
        carrierRepository.save(carrier);

        BaoGuanFee baoGuanFee = baoGuanFeeRepository.findByBelongId(carrier.getId());
        if (baoGuanFee == null) {
            baoGuanFee = new BaoGuanFee();
            baoGuanFee.setId(snowFlakeFactory.nextId("BG"));
            baoGuanFee.setBelongId(carrier.getId());
        }
        baoGuanFee.setHomeFee(carrierCreateDto.getHomeFee());
        baoGuanFee.setSheetFee(carrierCreateDto.getSheetFee());
        baoGuanFeeRepository.save(baoGuanFee);

        Contract contract = new Contract();
        BeanUtils.copyProperties(carrierCreateDto.getContract(), contract);
        contract.setId(snowFlakeFactory.nextId("CT"));
        contract.setBelongId(carrier.getId());
        contractRepository.save(contract);
        rateRepository.deleteAllByBelongId(carrier.getId());
        if (carrierCreateDto.getRates() != null && carrierCreateDto.getRates().size() > 0) {
            List<ExchangeRate> rates = new ArrayList<>();
            carrierCreateDto.getRates().forEach(rate -> {
                ExchangeRate saveRate = new ExchangeRate();
                BeanUtils.copyProperties(rate, saveRate);
                saveRate.setId(snowFlakeFactory.nextId("RA"));
                saveRate.setBelongId(carrier.getId());
                rates.add(saveRate);
            });
            rateRepository.save(rates);
        }
        // 保存线路费用
        if (carrierCreateDto.getLinePriceImportDtoList() != null && carrierCreateDto.getLinePriceImportDtoList().size() > 0) {
            linePriceService.batchCreate(carrierCreateDto.getLinePriceImportDtoList(), carrier.getId(), carrierCreateDto.getOpId(), carrierCreateDto.getOrganizationId(), "应付");
        }
        User user = userRepository.findById(carrierCreateDto.getOpId());
        LogCreateDto createDto = new LogCreateDto(user.getId(), user.getNickName(), "创建供应商", carrier.getName());
        logService.create(createDto);
    }

    @Override
    public void update(CarrierUpdateDto updateDto) {
        Assert.hasText(updateDto.getCode(), "供应商编号不能为空");
        Assert.hasText(updateDto.getName(), "供应商简称不能为空");
        Assert.hasText(updateDto.getFullName(), "供应商全称不能为空");
        List<Carrier> carriers = carrierRepository.findByOrganizationId(updateDto.getOrganizationId());
        Assert.isTrue(carriers.stream().noneMatch(carrier -> !carrier.getId().equals(updateDto.getId()) && carrier.getName().equals(updateDto.getName())), String.format("当前机构已经存在相同供应商简称【%s】，请更改再保存", updateDto.getName()));
        Carrier carrier = carrierRepository.findById(updateDto.getId());
        BeanUtils.copyProperties(updateDto, carrier, "id");
        carrier.setCode(carrier.getCode().toUpperCase());
        carrierRepository.save(carrier);

        BaoGuanFee baoGuanFee = baoGuanFeeRepository.findByBelongId(carrier.getId());
        if (baoGuanFee == null) {
            baoGuanFee = new BaoGuanFee();
            baoGuanFee.setId(snowFlakeFactory.nextId("BG"));
            baoGuanFee.setBelongId(carrier.getId());
        }
        baoGuanFee.setHomeFee(updateDto.getHomeFee());
        baoGuanFee.setSheetFee(updateDto.getSheetFee());
        baoGuanFeeRepository.save(baoGuanFee);

        Contract contract = contractRepository.findByBelongId(carrier.getId());
        BeanUtils.copyProperties(updateDto.getContract(), contract, "id");
        contractRepository.save(contract);
        rateRepository.deleteAllByBelongId(carrier.getId());
        if (updateDto.getRates() != null && updateDto.getRates().size() > 0) {
            List<ExchangeRate> rates = new ArrayList<>();
            updateDto.getRates().forEach(rate -> {
                ExchangeRate saveRate = new ExchangeRate();
                BeanUtils.copyProperties(rate, saveRate);
                saveRate.setId(snowFlakeFactory.nextId("RA"));
                saveRate.setBelongId(carrier.getId());
                rates.add(saveRate);
            });
            rateRepository.save(rates);
        }
        // 保存车型派送费
        List<ClientVehiclePriceDto> vehiclePrices = updateDto.getVehiclePrices().stream().filter(dto -> !dto.getId().contains("VP") && StringUtils.hasText(dto.getName())).collect(Collectors.toList());
        if (vehiclePrices.size() > 0) {
            for (ClientVehiclePriceDto vehiclePrice : vehiclePrices) {
                ClientVehiclePrice clientVehiclePrice = new ClientVehiclePrice();
                clientVehiclePrice.setBelongId(carrier.getId());
                clientVehiclePrice.setId(snowFlakeFactory.nextId("VP"));
                clientVehiclePrice.setName(vehiclePrice.getName());
                clientVehiclePrice.setAmount(vehiclePrice.getAmount());
                clientVehiclePrice.setStartPlace(vehiclePrice.getStartPlace());
                clientVehiclePrice.setEndPlace(vehiclePrice.getEndPlace());
                clientVehiclePriceRepository.save(clientVehiclePrice);
            }
        }
        // 更新原有的
        vehiclePrices =
                updateDto.getVehiclePrices().stream().filter(dto -> dto.getId().contains("VP") && StringUtils.hasText(dto.getName())).collect(Collectors.toList());
        if(vehiclePrices.size() > 0){
            for (ClientVehiclePriceDto dto : vehiclePrices) {
                ClientVehiclePrice vehiclePrice = clientVehiclePriceRepository.getOne(dto.getId());
                vehiclePrice.setName(dto.getName());
                vehiclePrice.setStartPlace(dto.getStartPlace());
                vehiclePrice.setEndPlace(dto.getEndPlace());
                vehiclePrice.setAmount(dto.getAmount());
                clientVehiclePriceRepository.save(vehiclePrice);
            }
        }
        User user = userRepository.findById(updateDto.getOpId());
        LogCreateDto createDto = new LogCreateDto(user.getId(), user.getNickName(), "修改供应商", carrier.getName());
        logService.create(createDto);
    }

    @Override
    public PageResult<CarrierDto> query(CarrierQueryDto params) {
        List<Organization> organizations = organizationService.getAll();
        Specification<Carrier> specification = ((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            Predicate organizationId = criteriaBuilder.equal(root.get("organizationId"), params.getCurrentOrgId());
            predicates.add(organizationId);
            if (!StringUtils.isEmpty(params.getCode())) {
                Predicate site = criteriaBuilder.like(root.get("code"), "%" + params.getCode().trim() + "%");
                predicates.add(site);
            }
            if (!StringUtils.isEmpty(params.getFullName())) {
                Predicate site = criteriaBuilder.like(root.get("fullName"), "%" + params.getFullName().trim() + "%");
                predicates.add(site);
            }
            if (!StringUtils.isEmpty(params.getName())) {
                Predicate site = criteriaBuilder.like(root.get("name"), "%" + params.getName().trim() + "%");
                predicates.add(site);
            }
            if (!StringUtils.isEmpty(params.getLicence())) {
                Predicate site = criteriaBuilder.like(root.get("licence"), "%" + params.getLicence().trim() + "%");
                predicates.add(site);
            }
            if (params.getCreateTimeStart() != null) {
                Predicate timeStart = criteriaBuilder.greaterThanOrEqualTo(root.get("createTime"), params.getCreateTimeStart().atStartOfDay());
                predicates.add(timeStart);
            }
            if (params.getCreateTimeEnd() != null) {
                Predicate timeEnd = criteriaBuilder.lessThanOrEqualTo(root.get("createTime"), params.getCreateTimeEnd().plusDays(1));
                predicates.add(timeEnd);
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        });
        Sort sort = new Sort(Sort.Direction.DESC, "createTime");
        Pageable pageable = new PageRequest(params.getPage() - 1, params.getPageSize(), sort);
        Page<Carrier> details = carrierRepository.findAll(specification, pageable);
        List<Carrier> carriers = details.getContent();
        List<String> clientIds = carriers.stream().map(Carrier::getId).collect(Collectors.toList());
        List<CarrierDto> carrierDtos = new ArrayList<>();
        List<Contract> contracts = contractRepository.findByBelongIdIn(clientIds);
        if (carriers.size() > 0) {
            carriers.forEach(carrier -> {
                CarrierDto dto = new CarrierDto();
                BaoGuanFee baoGuanFee = baoGuanFeeRepository.findByBelongId(carrier.getId());
                BeanUtils.copyProperties(carrier, dto);
                if (baoGuanFee != null) {
                    dto.setHomeFee(baoGuanFee.getHomeFee());
                    dto.setSheetFee(baoGuanFee.getSheetFee());
                }
                organizations.stream().filter(organization -> organization.getId().equals(carrier.getOrganizationId())).findFirst().ifPresent(organization -> dto.setTaiTou(organization.getName()));
                contracts.stream().filter(contract -> contract.getBelongId().equals(carrier.getId())).findFirst().ifPresent(contract -> {
                    BeanUtils.copyProperties(contract, dto, "id");
                });
                carrierDtos.add(dto);
            });
        }
        return PageResult.of(carrierDtos, params.getPage(), params.getPageSize(), details.getTotalElements());
    }

    @Override
    public void delete(String id, String userId) {
        Carrier carrier = carrierRepository.findById(id);
        carrierRepository.delete(carrier);
        contractRepository.deleteByBelongId(id);
        rateRepository.deleteAllByBelongId(id);
        User user = userRepository.findById(userId);
        LogCreateDto createDto = new LogCreateDto(user.getId(), user.getNickName(), "删除供应商", carrier.getName());
        logService.create(createDto);
    }

    @Override
    public CarrierDetailDto getById(String id) {
        Carrier carrier = carrierRepository.findById(id);
        CarrierDetailDto carrierDetailDto = new CarrierDetailDto();
        BeanUtils.copyProperties(carrier, carrierDetailDto);
        Contract contract = contractRepository.findByBelongId(id);
        CarrierDetailDto.ContractDto contractDto = new CarrierDetailDto.ContractDto();
        BeanUtils.copyProperties(contract, contractDto);
        carrierDetailDto.setContract(contractDto);
        List<ExchangeRate> rates = rateRepository.findByBelongId(id);
        List<CarrierDetailDto.ExchangeRateDto> rateDtos = new ArrayList<>();
        if (rates.size() > 0) {
            rates.forEach(exchangeRate -> {
                CarrierDetailDto.ExchangeRateDto dto = new CarrierDetailDto.ExchangeRateDto();
                BeanUtils.copyProperties(exchangeRate, dto);
                rateDtos.add(dto);
            });
            carrierDetailDto.setRates(rateDtos);
        }
        return carrierDetailDto;
    }

    @Override
    public List<CarrierDto> getAll() {
        List<CarrierDto> carrierDtos = new ArrayList<>();
        List<Carrier> carriers = carrierRepository.findAll();
        List<String> orgIds = carriers.stream().map(Carrier::getOrganizationId).collect(Collectors.toList());
        List<Organization> organizations = organizationService.getAllByIds(orgIds);
        carriers.forEach(carrier -> {
            CarrierDto dto = new CarrierDto();
            BeanUtils.copyProperties(carrier, dto);
            organizations.stream().filter(organization -> organization.getId().equals(carrier.getOrganizationId())).findFirst().ifPresent(organization -> dto.setOrganizationName(organization.getName()));
            carrierDtos.add(dto);
        });
        return carrierDtos;
    }

    @Override
    public void batchCreate(List<CarrierImportDto> importDatas, String opId) {
        User user = userRepository.findById(opId);
        List<Organization> organizations = organizationService.getAll();
        List<Carrier> carriers = carrierRepository.findAll();
        for (CarrierImportDto importData : importDatas) {
            boolean noneMatch = carriers.stream().noneMatch(carrier -> carrier.getCode().equals(importData.getCode()));
            Assert.isTrue(noneMatch, "系统存在供应商代码[" + importData.getCode() + "]");
            noneMatch = carriers.stream().noneMatch(carrier -> carrier.getName().equals(importData.getName()));
            Assert.isTrue(noneMatch, "系统存在供应商简称[" + importData.getName() + "]");
            if (StringUtils.hasText(importData.getTaiTou())) {
                boolean anyMatch = organizations.stream().anyMatch(organization -> organization.getName().equals(importData.getTaiTou()));
                Assert.isTrue(anyMatch, "没有找到机构抬头为[" + importData.getTaiTou() + "]的记录");
            }
        }
        for (CarrierImportDto importData : importDatas) {
            CarrierCreateDto createDto = new CarrierCreateDto();
            BeanUtils.copyProperties(importData, createDto);
            CarrierCreateDto.ContractDto contract = new CarrierCreateDto.ContractDto();
            BeanUtils.copyProperties(importData, contract);
            if (importData.getSignedDate() != null) {
                Instant instant = importData.getSignedDate().toInstant();
                ZoneId zoneId = ZoneId.systemDefault();
                // atZone()方法返回在指定时区从此Instant生成的ZonedDateTime
                contract.setSignedDate(instant.atZone(zoneId).toLocalDate());
            }
            if (importData.getExpiresDate() != null) {
                Instant instant = importData.getExpiresDate().toInstant();
                ZoneId zoneId = ZoneId.systemDefault();
                // atZone()方法返回在指定时区从此Instant生成的ZonedDateTime
                contract.setExpiresDate(instant.atZone(zoneId).toLocalDate());
            }
            if (importData.getRemindDate() != null) {
                Instant instant = importData.getRemindDate().toInstant();
                ZoneId zoneId = ZoneId.systemDefault();
                // atZone()方法返回在指定时区从此Instant生成的ZonedDateTime
                contract.setRemindDate(instant.atZone(zoneId).toLocalDate());
            }
            createDto.setContract(contract);
            organizations.stream().filter(organization -> organization.getName().equals(importData.getTaiTou())).findFirst().ifPresent(organization -> createDto.setOrganizationId(organization.getId()));
            if (StringUtils.isEmpty(createDto.getOrganizationId())) {
                createDto.setOrganizationId(user.getOrganizationId());
            }
            createDto.setOpId(opId);
            this.create(createDto);
        }
    }

    @Override
    public List<CarrierDto> queryByOrgId(String orgId) {
        List<CarrierDto> carrierDtos = new ArrayList<>();
        List<Carrier> carriers = carrierRepository.findByOrganizationIdIn(Collections.singletonList(orgId));
        carriers.forEach(carrier -> {
            CarrierDto dto = new CarrierDto();
            BeanUtils.copyProperties(carrier, dto);
            carrierDtos.add(dto);
        });
        return carrierDtos;
    }

    @Override
    public List<ClientVehiclePriceDto> getVehiclePrices(VehicleFeeQueryDto params) {
        Specification<ClientVehiclePrice> specification = ((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            Predicate belongId = criteriaBuilder.equal(root.get("belongId"), params.getOwnerId());
            predicates.add(belongId);
            if (!StringUtils.isEmpty(params.getName())) {
                Predicate name = criteriaBuilder.like(root.get("name"), "%" + params.getName().trim() + "%");
                predicates.add(name);
            }
            if (!StringUtils.isEmpty(params.getStartPlace())) {
                Predicate startPlace = criteriaBuilder.like(root.get("startPlace"), "%" + params.getStartPlace().trim() + "%");
                predicates.add(startPlace);
            }
            if (!StringUtils.isEmpty(params.getEndPlace())) {
                Predicate endPlace = criteriaBuilder.like(root.get("endPlace"), "%" + params.getEndPlace().trim() + "%");
                predicates.add(endPlace);
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        });
        Sort sort = new Sort(Sort.Direction.ASC, "name");
        List<ClientVehiclePrice> clientVehiclePrices = clientVehiclePriceRepository.findAll(specification, sort);
        List<ClientVehiclePriceDto> priceDtos = new ArrayList<>();
        for (ClientVehiclePrice vehicle : clientVehiclePrices) {
            ClientVehiclePriceDto dto = new ClientVehiclePriceDto();
            BeanUtils.copyProperties(vehicle, dto);
            priceDtos.add(dto);
        }
        return priceDtos;
    }

    @Override
    public void batchCreateVehicleList(List<CarrierVehicleFeeImportDto> importDatas, String belongId) {
        List<Vehicle> vehicles = vehicleRepository.findAll();
        List<ClientVehiclePrice> clientVehiclePrices = clientVehiclePriceRepository.findByBelongId(belongId);
        Map<String, List<CarrierVehicleFeeImportDto>> listMap = importDatas.stream().collect(Collectors.groupingBy(importData -> importData.getName() + importData.getStartPlace() + importData.getEndPlace()));
        listMap.forEach((key, dataList) -> {
            CarrierVehicleFeeImportDto importData = dataList.get(0);
            boolean hasVehicleRecord = vehicles.stream().anyMatch(vehicle -> vehicle.getName().equals(importData.getName()));
            if (!hasVehicleRecord) {
                Vehicle vehicle = new Vehicle();
                vehicle.setId(snowFlakeFactory.nextId("VC"));
                vehicle.setName(importData.getName());
                vehicleRepository.save(vehicles);
            }
            Optional<ClientVehiclePrice> optional = clientVehiclePrices.stream().filter(record -> record.getName().equals(importData.getName()) && record.getStartPlace().equals(importData.getStartPlace()) && record.getEndPlace().equals(importData.getEndPlace())).findFirst();
            if (optional.isPresent()) {
                ClientVehiclePrice save = optional.get();
                save.setAmount(importData.getAmount());
                clientVehiclePriceRepository.save(save);
            }
            ClientVehiclePrice clientVehiclePrice = new ClientVehiclePrice();
            clientVehiclePrice.setBelongId(belongId);
            clientVehiclePrice.setId(snowFlakeFactory.nextId("VP"));
            clientVehiclePrice.setName(importData.getName());
            if (importData.getAmount() != null) {
                clientVehiclePrice.setAmount(importData.getAmount());
            } else {
                clientVehiclePrice.setAmount(0d);
            }
            clientVehiclePrice.setStartPlace(importData.getStartPlace());
            clientVehiclePrice.setEndPlace(importData.getEndPlace());
            clientVehiclePriceRepository.save(clientVehiclePrice);
        });
    }
}
